import re
import os
import io
import base64
import zipfile
import datetime
from urllib.parse import urlparse
from dateutil import tz as dateutil_tz
from pathlib import Path

import arrow
from openpyxl.utils.exceptions import IllegalCharacterError
from openpyxl.styles import Alignment

from app.config.settings import DATETIME_FMT, REPORT_FILE_DIR


ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')


def decode_b64str(b64str):
    try:
        # 把"\x00" 替换为空， 否则模板加载出错
        result = base64.b64decode(b64str.encode()).decode("utf-8").replace("\x00", "")
        return result
    except Exception as e:
        return ""


def request_response_info(request, response):
    """
    漏洞数据从请求和响应结果中解析请求体和响应体, 方便写入word，excel
    """
    # 请求体
    body = decode_b64str(request.get("b64content", ""))
    method = request.get('method', "")
    u_parse = urlparse(request.get("url", ""))
    url = f"{u_parse.path}?{u_parse.query}" if u_parse.path and u_parse.query else ""
    http = response.get("version", "")
    req_headers_list = [f"{key}:{value}" for key, value in request.get("headers", {}).items()]
    req_headers = '\n'.join(req_headers_list)
    req_joins = [method, url, http]
    req_data = " ".join(req_joins) if request else ""
    req_data = f"{req_data}\n{req_headers}"
    if body:
        req_data = f"{req_data}\n\n{req_data}"

    # 响应体
    body_resp = decode_b64str(response.get("b64content", ""))
    status_code = str(int(response.get("status_code", -1)))
    res_headers_list = [f"{key}:{value}" for key, value in response.get("headers", {}).items()]
    res_joins = [http, status_code]
    res_headers = '\n'.join(res_headers_list)
    res_data = " ".join(res_joins) if response else ""
    res_data = f"{res_data}\n{res_headers}"
    if body_resp:
        if body_resp.startswith('%PDF') and body_resp.endswith('%%EOF'):
            body_resp = ''
        res_data = f"{res_data}\n\n{body_resp}"

    if req_data == '\n':
        req_data = ''
    if res_data == '\n':
        res_data = ''
    return req_data, res_data


def write_sheet(worker_excel, active, title, headers, data, horizontal="left"):
    """向excel sheet 中写入数据"""

    def remove_illegal_characters(_value):
        if re.search(ILLEGAL_CHARACTERS_RE, _value):
            _value = re.sub(ILLEGAL_CHARACTERS_RE, '', _value)
            return _value

    if not isinstance(data, list):
        raise ValueError("data is not list")
    if active == 0:
        worker_excel.active.title = title
    else:
        worker_excel.create_sheet(title)
    worker_excel.active = active
    for index, header in enumerate(headers):
        worker_excel.active.cell(row=1, column=index + 1, value=header)
    for index, line in enumerate(data):
        for col, value in enumerate(line):
            try:
                worker_excel.active.cell(row=index + 2, column=col + 1, value=value)
            except IllegalCharacterError:
                cleaned_value = remove_illegal_characters(value)
                worker_excel.active.cell(row=index + 2, column=col + 1, value=cleaned_value)
            worker_excel.active.cell(row=index + 2, column=col + 1).alignment = Alignment(horizontal=horizontal)
    active += 1
    return worker_excel, active


def local_time_to_utc(t):
    """本地时间字符串转换成utc时间"""
    if not t:
        return
    # 格式化为不带时区的字符串
    try:
        t = arrow.get(t).datetime.strftime(DATETIME_FMT)
    except arrow.parser.ParserError:
        return
    return arrow.get(t, tzinfo=dateutil_tz.tzlocal()).to(tz=dateutil_tz.tzutc()).datetime


def utc_time_to_local(t, is_format=False, fmt=DATETIME_FMT):
    """utc时间转换成本地时间"""
    t = arrow.get(t).to(tz=dateutil_tz.tzlocal()).datetime
    if is_format:
        return t.strftime(fmt)
    return t


def get_report_dir(task_id=None):
    if not os.path.exists(REPORT_FILE_DIR):
        os.makedirs(REPORT_FILE_DIR, exist_ok=True)
    if task_id:
        file_dir = os.path.join(REPORT_FILE_DIR, task_id)
        os.makedirs(file_dir, exist_ok=True)
        return file_dir
    else:
        return REPORT_FILE_DIR


def export_zip(file_list, path=None):
    if not path:
        stream = io.BytesIO()
    else:
        stream = path
    zfile = zipfile.ZipFile(stream, 'w', zipfile.ZIP_DEFLATED, allowZip64=False)
    for file in file_list:
        if os.path.isfile(file):
            zfile.write(file, os.path.basename(file))
    zfile.close()
    if not path:
        stream.seek(0)
    return stream


def format_ratio(x=None, y=None, value=None, is_100=False):
    if value is not None:
        value = value
    else:
        value = x/y
    if is_100:
        return float(format(value * 100, ".2f"))
    return float(format(value, ".4f"))


def check_report_filename(job_name, job_domain, base_dir):
    """
    先尝试使用资产名称做为报告名称， 如果不合规， 则使用资产域名做为报告名称。
    可能会有重复，在文件后边加上时间字符串
    """
    job_name = job_name.strip()
    time_str = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    filename = f"[{job_name}]ScanV安全监测报告{time_str}.docx"
    word_path = os.path.join(base_dir, filename)
    try:
        Path(word_path).touch()
    except FileNotFoundError:
        filename = f"[{job_domain}]ScanV安全监测报告{time_str}.docx"
        word_path = os.path.join(base_dir, filename)
    else:
        os.remove(word_path)
    return filename, word_path
